Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow HTTP::Client to work with any IO #9543

Merged
merged 5 commits into from
Jul 1, 2020

Conversation

waj
Copy link
Member

@waj waj commented Jun 24, 2020

I know there was attempts to make HTTP::Client more generic. In the long term that would be desirable to support connection pools or socket factories. But this change was so easy and allow other scenarios currently unsupported. For example, talk through UNIX socket. The added initializer looks ugly because still needs to fill host and port but those are used for the Host header anyway. Another option is just make the @socket available through a property so it can be externally set. The issue I found is that if close is called the next request will try to open a TCPSocket.

Dunno, again this feel incomplete but it's a simple change that open possibilities.

@waj waj marked this pull request as ready for review June 24, 2020 04:33
@jhass
Copy link
Member

jhass commented Jun 24, 2020

The issue I found is that if close is called the next request will try to open a TCPSocket.

Wait what? That sounds like a big foot gun right there. I think calling close should make the HTTP::Client instance unusable, regardless of this change.

Tests? :)

@asterite
Copy link
Member

Also documentation of this method, and maybe an embedded example 🙇

src/http/client.cr Outdated Show resolved Hide resolved
@straight-shoota
Copy link
Member

I had actually thought about this some time agoe. But it couldn't work without #9210.
It makes sense to use an IO directly and I suppose this could also continue when #6011 is resolved. Although most use cases should probably be served by that.

As @jhass mentioned, the #close behaviour needs to be fixed. IMO it would probably be best to close the client completely and not be able to reconnect. That would be a breaking change, though. So maybe for now we could just set a flag on the new initializer which disables automatic socket creation.

@asterite
Copy link
Member

Why can't we make breaking changes when we release 1.0.0? In my mind that release sounds like the biggest breaking change ever so that later there are no breaking changes.

Co-authored-by: Sijawusz Pur Rahnama <[email protected]>
@waj
Copy link
Member Author

waj commented Jun 24, 2020

We can make breaking changes, but those might not have good time of deprecation notice. I wouldn't avoid doing it anyway if we think it's worth it.

@jhass
Copy link
Member

jhass commented Jun 24, 2020

I think this wouldn't be a huge breaking change, as being able to reuse a closeed HTTP::Client feels like it ought to be against expectations anyways.

@waj
Copy link
Member Author

waj commented Jun 24, 2020

The socket is re-created because the api can be used like this:

HTTP::Client.new(...) do |client|
  client.get ...
  client.post ...
end

Within the block the connection might be the same or not, depending on wether the server/protocol supports the keep alive. Or the connection might be a different one, if a connection pool were implemented.

Then the close method is available, so the same behaviour can be obtained when the block syntax can't be used. Maybe we can differentiate an internal close or just set an internal flag to allow re-creation of the connection. That would work as @straight-shoota suggest. And also I don't think it's a big breaking change except for those explicitly calling close but still reusing the client instance.

@waj
Copy link
Member Author

waj commented Jun 24, 2020

I just implemented what @straight-shoota suggested, disabling autoreconnection only when initialized with IO, so this shouldn't be a breaking change. Also added specs and docs. We can still change how close works if we want, but I would do it in a separate PR. Note that we currently even have that behaviour documented:

crystal/src/http/client.cr

Lines 747 to 748 in 476486e

# Closes this client. If used again, a new connection will be opened.
def close

Should we change the name of the instance var? @socket -> @io

@waj waj changed the title [RFC] Allow HTTP socket to be any IO Allow HTTP::Client to work with any IO Jun 24, 2020
Copy link
Member

@jhass jhass left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good then now. I think the name of the instance var is fine, we can the noise of renaming it to a bigger refactor.

I still would prefer close to set @reconnect = false ASAP. Despite being documented right now otherwise, I find that behavior still counter intuitive. We could offer a connect kind of API to set/create the socket, keeping @reconnect to false for calls like connect(some_io) but resetting it to true for calls like connect(some_host), sort of mirroring the HTTP::Server#bind API. But totally out of scope here.

@straight-shoota
Copy link
Member

We can still change how close works if we want, but I would do it in a separate PR.

Na, I wouldn't touch that until the entire HTTP::Client implementation gets revisited.

It's on my to-do-after-1.0 list. But the list is actually quite long. 😆

@straight-shoota
Copy link
Member

Ideally, when using with an IO the client should not set a default host header unless explicitly asked told. The current default localhost:80 is just an arbitrary value and may cause issues if the server tries to read something from that.
I guess it's probably fine for now, though. Changing that would require a lot more refactoring.

src/http/client.cr Outdated Show resolved Hide resolved
@waj
Copy link
Member Author

waj commented Jun 24, 2020

The Host header is mandatory in HTTP/1.1 and the client defaults to that version on every request. It's normally used only for virtual hosts so at most I think it would trigger a 404.

If you have ideas for the HTTP::Client post 1.0 I suggest we review them now, at least to tune the API to avoid breaking changes after the release. I'd love to see a more flexible HTTP client built-in in the std, but I don't have specific plans in mind.

@waj
Copy link
Member Author

waj commented Jun 24, 2020

Correction: according to the RFC, seems like the Host header is indeed mandatory, but it must be empty in those cases: https://tools.ietf.org/html/rfc2616#section-14.23. Setting @host = "" might work for now. I'll add that to this PR.

@straight-shoota
Copy link
Member

Yes, the host header is mandatory, but it should be empty if there's no meaningful value.

My ideas about HTTP::Client are basically expressed in #6011 and related discussions. There's also a PoC implementation in
#6001 and judging by that I'm relatively confident that we can change and enhance the implementation without breaking the public API, at least with temporary re-implementations to keep backwards compatibility.

@asterite
Copy link
Member

I think another thing that should happen is to have HTTP::Client.get and others use an internal pool of connections. So in general people shouldn't use HTTP::Client.new at all. (similar to Go, I guess)

@straight-shoota
Copy link
Member

@asterite Yes, that's actually one of the main features described in #6011

@waj
Copy link
Member Author

waj commented Jun 24, 2020

Well.. finally I renamed the ivar 🙈

@waj waj added this to the 1.0.0 milestone Jun 30, 2020
@asterite
Copy link
Member

Just a note that the old annotation, @socket : TCPSocket | OpenSSL::SSL::Socket | Nil, actually was already @socket : IO | Nil because TCPSocket and OpenSSL::SSL::Socket both inherit from IO so that was the actual type seen by the compiler.

I'd still like #9052 to be considered because it would fix that problem (the ivar would actually be that union type, never go to the parent type).

@bcardiff bcardiff merged commit 2f8092c into crystal-lang:master Jul 1, 2020
@waj waj deleted the http-client-io branch July 1, 2020 13:39
mamantoha added a commit to mamantoha/http_proxy that referenced this pull request Jul 7, 2020
@Darimac1

This comment has been minimized.

@Blacksmoke16

This comment has been minimized.

@Darimac1

This comment has been minimized.

@Darimac1

This comment has been minimized.

@Sija

This comment has been minimized.

@Darimac1

This comment has been minimized.

saltycrys added a commit to saltycrys/invidious that referenced this pull request Feb 2, 2021
Rename `HTTPClient@socket` to `HTTPClient@io`, see
crystal-lang/crystal#9543.

Rename `URI#full_path` to `URI#request_target`, see
crystal-lang/crystal#10099.
FireMasterK pushed a commit to StuffNoOneCaresAbout/invidious that referenced this pull request Feb 4, 2021
Rename `HTTPClient@socket` to `HTTPClient@io`, see
crystal-lang/crystal#9543.

Rename `URI#full_path` to `URI#request_target`, see
crystal-lang/crystal#10099.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants